/*
 * Copyright (c) 2013, marco.tamburelli@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bmi.gwt.mi.server.impl;

import java.lang.reflect.Array;
import java.util.Date;
import java.util.List;

import org.bmi.gwt.mi.shared.communication.ptypes.ArrayParam;
import org.bmi.gwt.mi.shared.communication.ptypes.BooleanParam;
import org.bmi.gwt.mi.shared.communication.ptypes.ByteParam;
import org.bmi.gwt.mi.shared.communication.ptypes.CharacterParam;
import org.bmi.gwt.mi.shared.communication.ptypes.DateParam;
import org.bmi.gwt.mi.shared.communication.ptypes.DoubleParam;
import org.bmi.gwt.mi.shared.communication.ptypes.FloatParam;
import org.bmi.gwt.mi.shared.communication.ptypes.IntegerParam;
import org.bmi.gwt.mi.shared.communication.ptypes.LongParam;
import org.bmi.gwt.mi.shared.communication.ptypes.ObjectParam;
import org.bmi.gwt.mi.shared.communication.ptypes.ObjectVarParam;
import org.bmi.gwt.mi.shared.communication.ptypes.Param;
import org.bmi.gwt.mi.shared.communication.ptypes.ShortParam;
import org.bmi.gwt.mi.shared.communication.ptypes.StringParam;
import org.bmi.gwt.mi.shared.communication.ptypes.array.ArrayArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.ArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.BooleanArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.ByteArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.CharacterArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.DateArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.DoubleArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.FloatArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.IntegerArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.LongArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.ObjectArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.ShortArrayWrapper;
import org.bmi.gwt.mi.shared.communication.ptypes.array.StringArrayWrapper;
import org.bmi.gwt.mi.shared.exceptions.InvalidArgumentException;
import org.bmi.gwt.mi.shared.util.ArrayUtils;
import org.bmi.gwt.mi.shared.util.ProxyBase;

import com.google.gwt.user.client.rpc.IsSerializable;

/** For internal use only */
public class ParamUtils
{
	public static Param<?> createParameter(Object p) throws InvalidArgumentException
	{
		if (p instanceof Boolean)
		{
			BooleanParam _p = new BooleanParam();
			_p.setValue((Boolean) p);

			return _p;
		}

		if (p instanceof Byte)
		{
			ByteParam _p = new ByteParam();
			_p.setValue((Byte) p);

			return _p;
		}

		if (p instanceof Character)
		{
			CharacterParam _p = new CharacterParam();
			_p.setValue((Character) p);

			return _p;
		}

		if (p instanceof Date)
		{
			DateParam _p = new DateParam();
			_p.setValue((Date) p);

			return _p;
		}

		if (p instanceof Double)
		{
			DoubleParam _p = new DoubleParam();
			_p.setValue((Double) p);

			return _p;
		}

		if (p instanceof Float)
		{
			FloatParam _p = new FloatParam();
			_p.setValue((Float) p);

			return _p;
		}

		if (p instanceof Integer)
		{
			IntegerParam _p = new IntegerParam();
			_p.setValue((Integer) p);

			return _p;
		}

		if (p instanceof Long)
		{
			LongParam _p = new LongParam();
			_p.setValue((Long) p);

			return _p;
		}

		if (p instanceof Short)
		{
			ShortParam _p = new ShortParam();
			_p.setValue((Short) p);

			return _p;
		}

		if (p instanceof String)
		{
			StringParam _p = new StringParam();
			_p.setValue((String) p);

			return _p;
		}

		if (p instanceof ProxyBase)
		{
			ObjectVarParam _p = new ObjectVarParam();
			_p.init(ProxyBase.getObjectId((ProxyBase) p));

			return _p;
		}

		if (p instanceof IsSerializable)
		{
			ObjectParam _p = new ObjectParam();
			_p.setValue((IsSerializable) p);

			return _p;
		}

		if (p.getClass().isArray())
		{
			ArrayParam _p = new ArrayParam();
			_p.setValue(ArrayUtils.wrapArray(p));

			return _p;
		}

		throw new InvalidArgumentException(p.getClass() + " is a type that cannot be serialized.");
	}

	/**
	 * Returns the value contained in the parameter envelope.
	 * 
	 * @param <T> the expected type of the wrapped element.
	 * 
	 * @param param A parameter envelope.
	 * @param type The expected type of the parameter
	 * @return
	 */
	public static Object getValue(Param<?> param, Class<?> type)
	{
		if (param instanceof ArrayParam) return unwrapArray(((ArrayParam) param).getValue(), type);

		return param.getValue();
	}

	/**
	 * Updates the value of a parameter envelope.
	 * 
	 * @param <T> The type wrapped by the {@link Param} object.
	 * 
	 * @param param A parameter envelope.
	 * @param value A value to be applied to the provided parameter envelope.
	 * @throws InvalidArgumentException
	 */
	public static <T> void setValue(Param<T> param, Object value) throws InvalidArgumentException
	{
		T currentValue = param.getValue();
		@SuppressWarnings("unchecked")
		Class<? extends T> valueType = (Class<? extends T>) currentValue.getClass();

		if (value instanceof Object[])
		{
			ArrayParam _p = (ArrayParam) param;
			Object[] params = (Object[]) value;

			_p.setValue(ArrayUtils.wrapArray(params));
			return;
		}

		try
		{
			param.setValue(valueType.cast(value));
		}
		catch (ClassCastException e)
		{
			throw new InvalidArgumentException("provided " + value.getClass() + " while " + valueType
					+ " was required.", e);
		}
	}

	private static Object unwrapArray(ArrayWrapper<?, ?> wrapper, Class<?> type)
	{
		Class<?> componentType = type.getComponentType();
		boolean isPr = componentType.isPrimitive();
		List<?> values = wrapper.getValues();

		if (wrapper instanceof BooleanArrayWrapper)
			return isPr ? asBoolnArray(values) : values.toArray(new Boolean[0]);

		if (wrapper instanceof ByteArrayWrapper) return isPr ? asByteArray(values) : values.toArray(new Byte[0]);

		if (wrapper instanceof CharacterArrayWrapper)
			return isPr ? asCharArray(values) : values.toArray(new Character[0]);

		if (wrapper instanceof DateArrayWrapper) return values.toArray(new Date[0]);

		if (wrapper instanceof DoubleArrayWrapper) return isPr ? asDoubleArray(values) : values.toArray(new Double[0]);

		if (wrapper instanceof FloatArrayWrapper) return isPr ? asFloatArray(values) : values.toArray(new Float[0]);

		if (wrapper instanceof IntegerArrayWrapper) return isPr ? asIntArray(values) : values.toArray(new Integer[0]);

		if (wrapper instanceof LongArrayWrapper) return isPr ? asLongArray(values) : values.toArray(new Long[0]);

		if (wrapper instanceof ObjectArrayWrapper)
		{
			Object[] generic = values.toArray();
			Object[] array = (Object[]) Array.newInstance(componentType, generic.length);

			for (int i = 0; i < generic.length; i++)
				array[i] = generic[i];

			return array;
		}

		if (wrapper instanceof ShortArrayWrapper) return isPr ? asShortArray(values) : values.toArray(new Short[0]);

		if (wrapper instanceof StringArrayWrapper) return values.toArray(new String[0]);

		if (wrapper instanceof ArrayArrayWrapper<?>)
		{
			Object[] array = (Object[]) Array.newInstance(componentType, values.size());

			int i = 0;
			for (Object value : values)
			{
				ArrayWrapper<?, ?> v = (ArrayWrapper<?, ?>) value;
				array[i++] = unwrapArray(v, componentType);
			}

			return array;
		}

		/* It shouldn't be possible */
		return null;
	}

	public static boolean[] asBoolnArray(List<?> list)
	{
		boolean[] array = new boolean[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Boolean) e;

		return array;
	}

	public static byte[] asByteArray(List<?> list)
	{
		byte[] array = new byte[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Byte) e;

		return array;
	}

	public static char[] asCharArray(List<?> list)
	{
		char[] array = new char[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Character) e;

		return array;
	}

	public static double[] asDoubleArray(List<?> list)
	{
		double[] array = new double[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Double) e;

		return array;
	}

	public static float[] asFloatArray(List<?> list)
	{
		float[] array = new float[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Float) e;

		return array;
	}

	public static int[] asIntArray(List<?> list)
	{
		int[] array = new int[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Integer) e;

		return array;
	}

	public static long[] asLongArray(List<?> list)
	{
		long[] array = new long[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Long) e;

		return array;
	}

	public static short[] asShortArray(List<?> list)
	{
		short[] array = new short[list.size()];

		int i = 0;
		for (Object e : list)
			array[i++] = (Short) e;

		return array;
	}
}
